 /*
  * $Header: /cvshome/build/org.osgi.service.application/src/org/osgi/service/application/ApplicationAdminPermission.java,v 1.34 2006/07/12 21:22:11 hargrave Exp $
  *
  * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */

 package org.osgi.service.application;

 import java.security.Permission ;
 import java.util.*;

 import org.osgi.framework.*;

 /**
  * This class implements permissions for manipulating applications and
  * their instances.
  * <P>
  * ApplicationAdminPermission can be targeted to applications that matches the
  * specified filter.
  * <P>
  * ApplicationAdminPermission may be granted for different actions:
  * <code>lifecycle</code>, <code>schedule</code> and <code>lock</code>.
  * The permission <code>schedule</code> implies the permission
  * <code>lifecycle</code>.
  */
 public class ApplicationAdminPermission extends Permission {
     private static final long serialVersionUID = 1L;
   
     /**
      * Allows the lifecycle management of the target applications.
      */
     public static final String LIFECYCLE_ACTION = "lifecycle";

     /**
      * Allows scheduling of the target applications. The permission to
      * schedule an application implies that the scheduler can also
      * manage the lifecycle of that application i.e. <code>schedule</code>
      * implies <code>lifecycle</code>
      */
     public static final String SCHEDULE_ACTION = "schedule";

     /**
      * Allows setting/unsetting the locking state of the target applications.
      */
     public static final String LOCK_ACTION = "lock";

     private ApplicationDescriptor applicationDescriptor;

     /**
      * Constructs an ApplicationAdminPermission. The <code>filter</code>
      * specifies the target application. The <code>filter</code> is an
      * LDAP-style filter, the recognized properties are <code>signer</code>
      * and <code>pid</code>. The pattern specified in the <code>signer</code>
      * is matched with the Distinguished Name chain used to sign the application.
      * Wildcards in a DN are not matched according to the filter string rules,
      * but according to the rules defined for a DN chain. The attribute
      * <code>pid</code> is matched with the PID of the application according to
      * the filter string rules.
      * <p>
      * If the <code>filter</code> is <code>null</code> then it matches
      * <code>"*"</code>. If
      * <code>actions</code> is <code>"*"</code> then it identifies all the
      * possible actions.
      *
      * @param filter
      * filter to identify application. The value <code>null</code>
      * is equivalent to <code>"*"</code> and it indicates "all application".
      * @param actions
      * comma-separated list of the desired actions granted on the
      * applications or "*" means all the actions. It must not be
      * <code>null</code>. The order of the actions in the list is
      * not significant.
      * @throws InvalidSyntaxException
      * is thrown if the specified <code>filter</code> is not syntactically
      * correct.
      *
      * @exception NullPointerException
      * is thrown if the actions parameter is <code>null</code>
      *
      * @see ApplicationDescriptor
      * @see org.osgi.framework.AdminPermission
      */
     public ApplicationAdminPermission(String filter, String actions) throws InvalidSyntaxException {
         super(filter == null ? "*" : filter);
         
         if( filter == null )
             filter = "*";
         
         if( actions == null )
             throw new NullPointerException ( "Action string cannot be null!" );
         
         this.applicationDescriptor = null;
         this.filter = (filter == null ? "*" : filter);
         this.actions = actions;

         if( !filter.equals( "*" ) && !filter.equals( "<<SELF>>" ) )
             FrameworkUtil.createFilter( this.filter ); // check if the filter is valid
 init();
     }
     
     /**
      * This contructor should be used when creating <code>ApplicationAdminPermission</code>
      * instance for <code>checkPermission</code> call.
      * @param application the tareget of the operation, it must not be <code>null</code>
      * @param actions the required operation. it must not be <code>null</code>
      * @throws NullPointerException if any of the arguments is null.
      */
     public ApplicationAdminPermission(ApplicationDescriptor application, String actions) {
         super(application.getApplicationId());
                 
         if( application == null || actions == null )
             throw new NullPointerException ( "ApplicationDescriptor and action string cannot be null!" );
         
         this.filter = application.getApplicationId();
         this.applicationDescriptor = application;
         this.actions = actions;
         
         init();
     }
     
     /**
      * This method can be used in the {@link java.security.ProtectionDomain}
      * implementation in the <code>implies</code> method to insert the
      * application ID of the current application into the permission being
      * checked. This enables the evaluation of the
      * <code>&lt;&lt;SELF&gt;&gt;</code> pseudo targets.
      * @param applicationId the ID of the current application.
      * @return the permission updated with the ID of the current application
      */
     public ApplicationAdminPermission setCurrentApplicationId(String applicationId) {
         ApplicationAdminPermission newPerm = null;
         
         if( this.applicationDescriptor == null ) {
             try {
                 newPerm = new ApplicationAdminPermission( this.filter, this.actions );
             }catch( InvalidSyntaxException e ) {
                 throw new RuntimeException ( "Internal error" ); /* this can never happen */
             }
         }
         else
             newPerm = new ApplicationAdminPermission( this.applicationDescriptor, this.actions );
         
         newPerm.applicationID = applicationId;
         
         return newPerm;
     }

     /**
      * Checks if the specified <code>permission</code> is implied by this permission.
      * The method returns true under the following conditions:
      * <UL>
      * <LI> This permission was created by specifying a filter (see {@link #ApplicationAdminPermission(String, String)})
      * <LI> The implied <code>otherPermission</code> was created for a particular {@link ApplicationDescriptor}
      * (see {@link #ApplicationAdminPermission(ApplicationDescriptor, String)})
      * <LI> The <code>filter</code> of this permission mathes the <code>ApplicationDescriptor</code> specified
      * in the <code>otherPermission</code>. If the filter in this permission is the
      * <code>&lt;&lt;SELF&gt;&gt;</code> pseudo target, then the currentApplicationId set in the
      * <code>otherPermission</code> is compared to the application Id of the target
      * <code>ApplicationDescriptor</code>.
      * <LI> The list of permitted actions in this permission contains all actions required in the
      * <code>otherPermission</code>
      * </UL>
      * Otherwise the method returns false.
      * @param otherPermission the implied permission
      * @return true if this permission implies the <code>otherPermission</code>, false otherwise.
      */
   public boolean implies(Permission otherPermission) {
       if( otherPermission == null )
         return false;
         
       if(!(otherPermission instanceof ApplicationAdminPermission))
           return false;

       ApplicationAdminPermission other = (ApplicationAdminPermission) otherPermission;

       if( !filter.equals("*") ) {
         if( other.applicationDescriptor == null )
             return false;
         
         if( filter.equals( "<<SELF>>") ) {
             if( other.applicationID == null )
                 return false; /* it cannot be, this might be a bug */
             
             if( !other.applicationID.equals( other.applicationDescriptor.getApplicationId() ) )
                 return false;
         }
         else {
             Hashtable props = new Hashtable();
             props.put( "pid", other.applicationDescriptor.getApplicationId() );
             props.put( "signer", new SignerWrapper( other.applicationDescriptor ) );
                         
             Filter flt = getFilter();
             if( flt == null )
                 return false;
             
             if( !flt.match( props ) )
                 return false;
         }
       }
       
       if( !actionsVector.containsAll( other.actionsVector ) )
         return false;
       
       return true;
   }

   public boolean equals(Object with) {
     if( with == null || !(with instanceof ApplicationAdminPermission) )
         return false;
     
     ApplicationAdminPermission other = (ApplicationAdminPermission)with;
     
     // Compare actions:
 if( other.actionsVector.size() != actionsVector.size() )
         return false;
     
     for( int i=0; i != actionsVector.size(); i++ )
         if( !other.actionsVector.contains( actionsVector.get( i ) ) )
             return false;
     
     
     return equal(this.filter, other.filter ) && equal(this.applicationDescriptor, other.applicationDescriptor)
             && equal(this.applicationID, other.applicationID);
   }
   
   /**
    * Compares parameters for equality. If both object are null, they are considered
    * equal.
    * @param a object to compare
    * @param b other object to compare
    * @return true if both objects are equal or both are null
    */
   private static boolean equal(Object a, Object b) {
       // This equation is true if both references are null or both point
 // to the same object. In both cases they are considered as equal.
 if( a == b ) {
           return true;
       }
       
       return a.equals(b);
   }

   public int hashCode() {
       int hc = 0;
       for( int i=0; i != actionsVector.size(); i++ )
           hc ^= ((String )actionsVector.get( i )).hashCode();
       hc ^= (null == this.filter )? 0 : this.filter.hashCode();
       hc ^= (null == this.applicationDescriptor) ? 0 : this.applicationDescriptor.hashCode();
       hc ^= (null == this.applicationID) ? 0 : this.applicationID.hashCode();
       return hc;
   }

   /**
    * Returns the actions of this permission.
    * @return the actions specified when this permission was created
    */
   public String getActions() {
     return actions;
   }

   private String applicationID;

   private static final Vector ACTIONS = new Vector();
   private Vector actionsVector;
   private final String filter;
   private final String actions;
   private Filter appliedFilter = null;
   
   static {
       ACTIONS.add(LIFECYCLE_ACTION);
       ACTIONS.add(SCHEDULE_ACTION);
       ACTIONS.add(LOCK_ACTION);
   }

   private static Vector actionsVector(String actions) {
       Vector v = new Vector();
       StringTokenizer t = new StringTokenizer(actions.toUpperCase(), ",");
       while (t.hasMoreTokens()) {
           String action = t.nextToken().trim();
           v.add(action.toLowerCase());
       }
       
       if( v.contains( SCHEDULE_ACTION ) && !v.contains( LIFECYCLE_ACTION ) )
           v.add( LIFECYCLE_ACTION );
       
       return v;
   }
   

   private static class SignerWrapper extends Object {
     private String pattern;
     private ApplicationDescriptor appDesc;
     
     public SignerWrapper(String pattern) {
         this.pattern = pattern;
     }
     
     SignerWrapper(ApplicationDescriptor appDesc) {
         this.appDesc = appDesc;
     }
     
     public boolean equals(Object o) {
         if (!(o instanceof SignerWrapper))
             return false;
         SignerWrapper other = (SignerWrapper) o;
         ApplicationDescriptor matchAppDesc = (ApplicationDescriptor) (appDesc != null ? appDesc : other.appDesc);
         String matchPattern = appDesc != null ? other.pattern : pattern;
         return matchAppDesc.matchDNChain(matchPattern);
     }
   }
   
   private void init() {
         actionsVector = actionsVector( actions );

         if ( actions.equals("*") )
             actionsVector = actionsVector( LIFECYCLE_ACTION + "," + SCHEDULE_ACTION + "," + LOCK_ACTION );
         else if (!ACTIONS.containsAll(actionsVector))
       throw new IllegalArgumentException ("Illegal action!");
         
         applicationID = null;
   }
   
   private Filter getFilter() {
     String transformedFilter = filter;
     
     if (appliedFilter == null) {
         try {
             int pos = filter.indexOf("signer"); //$NON-NLS-1$
 if (pos != -1){
             
                 //there may be a signer attribute
 StringBuffer filterBuf = new StringBuffer (filter);
                 int numAsteriskFound = 0; //use as offset to replace in buffer

                 int walkbackPos; //temp pos

                 //find occurences of (signer= and escape out *'s
 while (pos != -1) {

                     //walk back and look for '(' to see if this is an attr
 walkbackPos = pos-1;
                     
                     //consume whitespace
 while(walkbackPos >= 0 && Character.isWhitespace(filter.charAt(walkbackPos))) {
                         walkbackPos--;
                     }
                     if (walkbackPos <0) {
                         //filter is invalid - FilterImpl will throw error
 break;
                     }
                     
                     //check to see if we have unescaped '('
 if (filter.charAt(walkbackPos) != '(' || (walkbackPos > 0 && filter.charAt(walkbackPos-1) == '\\')) {
                         //'(' was escaped or not there
 pos = filter.indexOf("signer",pos+6); //$NON-NLS-1$
 continue;
                     }
                     pos+=6; //skip over 'signer'

                     //found signer - consume whitespace before '='
 while (Character.isWhitespace(filter.charAt(pos))) {
                         pos++;
                     }

                     //look for '='
 if (filter.charAt(pos) != '=') {
                         //attr was signerx - keep looking
 pos = filter.indexOf("signer",pos); //$NON-NLS-1$
 continue;
                     }
                     pos++; //skip over '='

                     //found signer value - escape '*'s
 while (!(filter.charAt(pos) == ')' && filter.charAt(pos-1) != '\\')) {
                         if (filter.charAt(pos) == '*') {
                             filterBuf.insert(pos+numAsteriskFound,'\\');
                             numAsteriskFound++;
                         }
                         pos++;
                     }

                     //end of signer value - look for more?
 pos = filter.indexOf("signer",pos); //$NON-NLS-1$
 } //end while (pos != -1)
 transformedFilter = filterBuf.toString();
             } //end if (pos != -1)

             appliedFilter = FrameworkUtil.createFilter( transformedFilter );
         } catch (InvalidSyntaxException e) {
             //we will return null
 }
     }
     return appliedFilter;
   }
 }

